package MST;

import java.util.Arrays;
import java.util.Scanner;

/**
 * <p>
 * Kruskal算法相对于prime要易于理解一些，
 * 不过在学习kruskal算法之前需要补充并查集的前置知识
 * 相信在大家理解了并查集和贪心之后，本算法的实现便不难理解啦
 * 时间复杂度：O(eloge)
 *
 * </p>
 *
 * @author 没天赋
 * @since 2025-2-28
 */
public class Kruskal {
    //定义最大点数N,最大边数M
    public static final int N = (int) (1e5 + 5), M = (int) (1e5 + 5);

    //定义边数组,这里我们采用另一种常见的存图方法，
    // 即边x->y 权值为z的写类方法（具体名字叫啥忘了大家有知道的可以帮笔者补上hhh）
    public static class edge {
        public int x, y, z;
    }

    public static edge[] edges = new edge[M];
    //并查集fa数组
    public static int fa[] = new int[N];
    //点数n，边数m,下标id（记录边数）,入度u，出度v，权值w，最小生成树的总权值ans,一次贪心找到dis最小的点now
    public static int n, m, ans;
    public static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) {
        n = sin();
        m = sin();
        //初始化并查集，让每个节点指向自己
        for (int i = 1; i <= n; i++) fa[i] = i;
        //读入并存边
        for (int i = 1; i <= m; i++) {
            edges[i] = new edge();
            edges[i].x = sin();
            edges[i].y = sin();
            edges[i].z = sin();
        }
        //根据权值进行排序（贪心经常配排序哦~）
        Arrays.sort(edges, 1, m + 1,(edge o1, edge o2)-> o1.z - o2.z);
        //一共m条边，所以进行m次贪心
        for (int i = 1; i <= m; i++) {
            //查找x的根节点
            int fx = find(edges[i].x);
            //查找y的根节点
            int fy = find(edges[i].y);
            //如果根节点不相等，则合并
            if (fx != fy) {
                fa[fx] = fy;
                // 累加权值
                ans += edges[i].z;
            }
        }
        System.out.println(ans);
    }

    /**
     * 查找根节点，并进行路径压缩
     *
     * @param x
     * @return
     */
    public static int find(int x) {
        return fa[x] == x ? x : (fa[x] = find(fa[x]));
    }

    /**
     * 读取整数方法
     *
     * @return
     */
    public static int sin() {
        return sc.nextInt();
    }
}
